package cn.sylinx.hbatis.ext.starter.springboot;

import java.util.Collections;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import cn.sylinx.hbatis.ext.proxy.annotation.Command;
import cn.sylinx.hbatis.ext.starter.springboot.HbatisAutoConfig.AutoConfiguredCommandScannerRegistrar;
import cn.sylinx.hbatis.ext.starter.springboot.config.CacheProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.DefaultDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ExistDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.HbatisProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ModelpreloadProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.MultiDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ProxyProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.SqlResourcePreloadProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.StatementHandlerProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.TransactionProperties;
import cn.sylinx.hbatis.ext.starter.util.DefaultSimpleDaoService;
import cn.sylinx.hbatis.ext.starter.util.SimpleDaoService;
import cn.sylinx.hbatis.log.GLog;
import cn.sylinx.hbatis.plugin.IPlugin;
import cn.sylinx.hbatis.plugin.PluginStarter;

/**
 * 
 * <pre>
    #spring boot 使用已存在数据源的配置方法（不能和hbatis.init=true同时存在）
	hbatis.ds.exist=true
	hbatis.ds.dbtype=mysql
	
    #XmapperPlugin是否开启，hbatis.plugin.xmapper.inited=true表示开启
	hbatis.plugin.xmapper.inited=true
	hbatis.plugin.xmapper.resourcePath=xmapper
	
	#ModelPreloadPlugin是否开启，hbatis.plugin.modelpreload.inited=true表示开启
	hbatis.plugin.modelpreload.inited=true
	hbatis.plugin.modelpreload.scanPackageList=cn.sylinx.hbatis.springboot.starter
	hbatis.plugin.modelpreload.acmStrategy=cn.sylinx.hbatis.db.mapper.acm.UnderlinedAcmStrategy
	
	#EhcachePlugin是否开启，hbatis.plugin.ehcache.inited=true表示开启
	hbatis.plugin.ehcache.inited=true
	
	#SqlResourcePreloadPlugin插件，sql文件预加载目录
	hbatis.plugin.mirage.sqlpath=sql,var
	
	#TransactionIsolationPlugin插件，设置事务级别: (1、2、4、8)
	hbatis.plugin.transaction.level=2
	
	#第三方插件，可使用代理插件启动（第三方插件类名称，多个使用逗号‘,’隔开）
	hbatis.plugin.proxy.class=
 * </pre>
 * 
 * @author han
 *
 */
@Configuration
@Import({ AutoConfiguredCommandScannerRegistrar.class,
		cn.sylinx.hbatis.ext.transaction.springboot.TransactionAspect.class })
public class HbatisAutoConfig {

	@Bean
	public PluginStarter pluginStarter(@Autowired(required = false) DataSource dataSource,
			HbatisProperties hbatisProperties) {
		return create(hbatisProperties, dataSource);
	}

	private PluginStarter create(HbatisProperties hbatisProperties, DataSource dataSource) {

		List<IPlugin> pluginList = hbatisProperties.isInit() ? new PluginBuilder(hbatisProperties, dataSource) // 插件构建器
				.initDataSourcePlugin() // 初始化数据源
				.initModelPreLoadPlugin() // 模型预加载
				.initSqlResourcePreloadPlugin() // sql资源预加载
				.initStatementHandlerPlugin() // sql语句处理器
				.initTransactionPlugin() // 事务级别插件
				.initCachePlugin() // 缓存插件
				.initProxyPlugin() // 代理插件（第三方
				.initDebugPlugin() // 调试插件
				.build() : Collections.emptyList();

		PluginStarter pluginStarter = new PluginStarter(pluginList);
		pluginStarter.start();
		return pluginStarter;
	}

	/**
	 * 简单dao类处理
	 * 
	 * @return
	 */
	@Bean
	public SimpleDaoService simpleDaoService() {
		return new DefaultSimpleDaoService();
	}

	@Bean
	public HbatisProperties hbatisProperties(ExistDatasourceProperties existDatasourceProperties,
			DefaultDatasourceProperties defaultDatasourceProperties,
			MultiDatasourceProperties multiDatasourceProperties,
			SqlResourcePreloadProperties sqlResourcePreloadProperties, ModelpreloadProperties modelpreloadProperties,
			TransactionProperties transactionProperties, CacheProperties cacheProperties,
			StatementHandlerProperties statementHandlerProperties, ProxyProperties proxyProperties, Environment env) {

		GLog.debug("ExistDatasourceProperties:{}", existDatasourceProperties);
		GLog.debug("DefaultDatasourceProperties:{}", defaultDatasourceProperties);
		GLog.debug("MultiDatasourceProperties:{}", multiDatasourceProperties);
		GLog.debug("SqlResourcePreloadProperties:{}", sqlResourcePreloadProperties);
		GLog.debug("ModelpreloadProperties:{}", modelpreloadProperties);
		GLog.debug("TransactionProperties:{}", transactionProperties);
		GLog.debug("CacheProperties:{}", cacheProperties);
		GLog.debug("ProxyProperties:{}", proxyProperties);
		GLog.debug("StatementHandlerProperties:{}", statementHandlerProperties);

		HbatisProperties hp = new HbatisProperties();
		hp.setCacheProperties(cacheProperties);
		hp.setDefaultDatasource(defaultDatasourceProperties);
		hp.setInit(env.getProperty("hbatis.init", boolean.class, false));
		// 调试模式
		hp.setDebug(env.getProperty("hbatis.debug", boolean.class, false));
		hp.setModelpreloadProperties(modelpreloadProperties);
		hp.setMultiDatasource(multiDatasourceProperties);
		hp.setProxyProperties(proxyProperties);
		hp.setScanCommandPackages(env.getProperty("hbatis.command.scanpackage", String.class));
		hp.setSqlResourcePreloadProperties(sqlResourcePreloadProperties);
		hp.setTransactionProperties(transactionProperties);
		hp.setExistDatasourceProperties(existDatasourceProperties);
		hp.setStatementHandlerProperties(statementHandlerProperties);

		return hp;
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.plugin.cache")
	public CacheProperties getCacheProperties() {
		return new CacheProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.plugin.transaction")
	public TransactionProperties transactionProperties() {
		return new TransactionProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.plugin.modelpreload")
	public ModelpreloadProperties modelpreloadProperties() {
		return new ModelpreloadProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.plugin.mirage")
	public SqlResourcePreloadProperties sqlResourcePreloadProperties() {
		return new SqlResourcePreloadProperties();
	}

	@Bean
	public ProxyProperties ProxyProperties(Environment env) {
		String clazz = env.getProperty("hbatis.plugin.proxy.class");
		ProxyProperties pp = new ProxyProperties();
		pp.setClazz(clazz);
		return pp;
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.datasource.default")
	public DefaultDatasourceProperties defaultDatasourceProperties() {
		return new DefaultDatasourceProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.datasource.multi", ignoreInvalidFields = true)
	public MultiDatasourceProperties multiDatasourceProperties() {
		return new MultiDatasourceProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.ds")
	public ExistDatasourceProperties existDatasourceProperties() {
		return new ExistDatasourceProperties();
	}

	@Bean
	@ConfigurationProperties(prefix = "hbatis.plugin.statement")
	public StatementHandlerProperties statementHandlerProperties() {
		return new StatementHandlerProperties();
	}

	public static class AutoConfiguredCommandScannerRegistrar
			implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

		private BeanFactory beanFactory;

		private ResourceLoader resourceLoader;

		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {

			GLog.debug("Searching for command annotated with @Command");

			ClassPathCommandScanner scanner = new ClassPathCommandScanner(registry);

			try {
				if (this.resourceLoader != null) {
					scanner.setResourceLoader(this.resourceLoader);
				}

				List<String> packages = AutoConfigurationPackages.get(this.beanFactory);

				scanner.setAnnotationClass(Command.class);
				scanner.registerFilters();

				scanner.doScan(StringUtils.toStringArray(packages));

			} catch (IllegalStateException ex) {
				GLog.debug("Could not determine auto-configuration package, automatic command scanning disabled.", ex);
			}
		}

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			this.beanFactory = beanFactory;
		}

		@Override
		public void setResourceLoader(ResourceLoader resourceLoader) {
			this.resourceLoader = resourceLoader;
		}
	}

}
